/** 解析 eolink 相关工具 */

import _ from 'lodash'
import { Bit, SBit } from './constant'

/** api 状态 */
export const enum ApiStatus {
  /** 已发布 */
  PUBLISHED,
  /** 维护 */
  MAINTAIN,
  /** 废弃 */
  DEPRECATED = 2,
  /** 待定 */
  UNDEF = 3,
  /** 开发 */
  DEV = 4,
  Unkown5,
  /** 对接 */
  TODEV,
  UNKOWN7,
  /** 设置 */
  DESIGN = 8,
  /** 已完成 */
  FINISH = 9,
}

/** http method */
export enum ApiRequestType {
  POST = 0,
  GET = 1,
  PUT = 2,
  DEL = 3,
}

/** api 文档列表获取接口排序字段 */
export const enum ApiListOrderBy {
  /** 按名称排序 */
  NAMES = 0,
  /** 按更新时间 */
  UPDATE_TIME = 1,
  /** 按创建时间 */
  CREATE_TIME = 3,
  /** 按url排序 */
  URLS = 4,
}

/**
 * enum for biz status code
 * @readonly
 * @enum {string}
 */
export const enum StatusCode {
  // /** 用户请求参数错误 */
  // BAD_REQUEST = 'A0400',
  // /** 成功（OK） */
  OK = '000000',
  // /** 用户端错误  */
  // CLIENT_ERROR = 'A0001',
  // /** 用户账户被冻结 */
  // ACCOUNT_LOCKED = 'A0202',
  // /** 用户账户已作废 */
  // ACCOUNT_CANCEL = 'A0203',
  // /** 访问未授权 */
  UNAUTHORIZED = '200303',
  /** 可能是token过期  */
  TOKEN_EXPIRE = '400402',
  // /** 用户请求服务异常 */
  // INTERNAL_SERVER_ERROR = 'A0500'
}

// 解析 api info
export namespace ApiInfo {
  export const ParamTypeMap = [
    'string',
    'unknown1',
    'unknown2',
    'number', // int
    'number', // float
    'number', // double
    'string', // date
    'string', // date-time
    'boolean',
    'unknown9',
    'unknown10',
    'number', // long
    'object[]', // array
    'object',
    'number',
    'unknown15',
  ] as const
  export interface IApiInfo {
    authInfo: unknown
    baseInfo: IBaseInfo
    dataStructureList: Record<string, {
      structureID: number
      structureName: string
      structureDesc: string
      updateTime: string
      structureData: (IResultInfo & { paramID: string })[]
      structureType: string
      removed: Bit
    }>
    headerInfo: unknown[]
    requestInfo: IRequestInfo[]
    responseHeaders: unknown[]
    restfulParam: unknown[]
    /** 目前eolink resultInfo 字段的返回都是长度为1的tuple */
    resultInfo: [{
      responseID: unknown
      responseCode: unknown
      responseName: unknown
      responseType: unknown
      paramJsonType: unknown
      paramList: (IResultInfo | {
        structureID: number
        updateData: Record<string, {
          paramNotNull: SBit
          childList: IRequestInfo[]
          paramType: `${string & keyof typeof ParamTypeMap}`
        }>
      })[]
      raw: unknown
      binary: unknown
      isDefault: unknown
    }]
    resultParamJsonType: unknown
    resultParamType: unknown
    structureID: `[]` | `[${number}]`
    urlParam: IUrlParam[]
  }

  interface IBaseInfo {
    apiName: string
    apiURI: string
    apiRequestType: ApiRequestType
    apiStatus: ApiStatus
    /**
     * 请求参数类型
     * 0 - FormData
     * 2 - object
     */
    apiRequestParamType: 0 | 2
  }
  interface IRequestInfo extends IUrlParam {
    structureID?: `[]` | `[${number}]`
    childList: IRequestInfo[]
  }

  // interface IHeaderInfo {
  //   headerName: string
  //   headerValue: string
  //   paramNotNull: SBit
  //   default: 0
  //   paramType: typeof ParamTypeMap[number] | 'unknownType'
  //   paramName: string
  // }
  interface IResultInfo extends IRequestInfo {
  }
  // 使用 query 传参时使用这个参数
  interface IUrlParam {
    paramNotNull: SBit
    paramType: `${string & keyof typeof ParamTypeMap}`
    paramName: string
    paramID: string
    paramKey: string
    paramValue: unknown
    paramLimit: unknown
    paramNote: unknown
    paramValueList: unknown[]
    default: unknown
  }

  // 生成 jsdoc 字符串
  export function genJsDoc(apiInfo: ApiInfo.IApiInfo): string {

    const params = _.isEmpty(isQuery(apiInfo) ? apiInfo.urlParam : apiInfo.requestInfo) ? [] : [
      '@param {object} params',
      ...resolveListParams(isQuery(apiInfo) ? apiInfo.urlParam : apiInfo.requestInfo, apiInfo).map(param => `@param {${param.type}} ${param.nullable ? '[params.' + param.key.join('.') + ']' : 'params.' + param.key.join('.')
        } ${param.name}`),
    ]
    const results: string[] = []
    // void function traverse(items: ReturnType<typeof resolveResultInfo>) {
    //   for (const item of items) {
    //   }
    // }(resolveResultInfo(apiInfo))

    // legacy#begin 旧代码，能跑就不改
    const resolveReturn = (res: any[], paths: string[]) => {
      const _res: any[] = []
      const pad = _.pad('', paths.length * 2)
      _.each(res, (i) => {
        if (i.paramType == 12) {
          _res.push(
            pad + `${i.paramKey}: {`,
            ...resolveReturn(i.childList, [i.paramKey, ...paths]),
            pad + '}[]'
          )
        } else if (i.paramType == 13) {
          _res.push(
            pad + `${i.paramKey}: {`,
            ...resolveReturn(i.childList, [i.paramKey, ...paths]),
            pad + '}'
          )
        } else {
          _res.push(pad + `${i.paramKey}: ${ParamTypeMap[i.paramType]}`)
        }
      })
      return _res
    }
    const resolveJsDoc = (res: any[], paths: string[]) => {
      const _res: any[] = []
      const pad = _.pad('', paths.length * 2)
      _.each(res, (i) => {
        if (i.paramType == 12) {
          _res.push(
            pad + ` - ${i.paramKey}: ${i.paramName}`,
            ...resolveJsDoc(i.childList, [i.paramKey, ...paths])
          )
        } else if (i.paramType == 13) {
          _res.push(
            pad + ` - ${i.paramKey}: ${i.paramName}`,
            ...resolveJsDoc(i.childList, [i.paramKey, ...paths])
          )
        } else {
          _res.push(pad + ` - ${i.paramKey}: ${i.paramName}`)
        }
      })
      return _res
    }
    // legacy#end

    const _dataRaw = _(resolveResultInfo(apiInfo, true)).head()

    const returnVal = _.isEmpty(resolveResultInfo(apiInfo)) ? '' : `\n * @returns {Promise<{\n${resolveReturn(resolveResultInfo(apiInfo), []).map(ret => ` *   ${ret}`).join('\n')
      }\n * }${_dataRaw && ParamTypeMap[_dataRaw.paramType] == 'object[]' ? '[]' : ''
      }>} ** 返回体 **\n${resolveJsDoc(resolveResultInfo(apiInfo), []).map(ret => ` *  ${ret}`).join('\n')}`

    const jsDoc = _.template(_.trim(
      `
/**
 * <%= apiName %><%= _.isEmpty(params) ? '' : '\\n' %><%= _(params).map(param=>\` * \${param}\`).value().join('\\n') %><%= returnVal %>
 */
`
    ))({
      apiName: apiInfo.baseInfo.apiName,
      params,
      returnVal
    })
    return jsDoc
  }

  // 生成 ts dto 字符串
  export function genTsDto(apiInfo: ApiInfo.IApiInfo): string {
    const res = resolveListParams(isQuery(apiInfo) ? apiInfo.urlParam : apiInfo.requestInfo, apiInfo)
    if (_.isEmpty(res)) {
      return 'void'
    } else {
      let text = '{'
      const stks: typeof res = []
      for (const item of res) {
        const pad = _.pad('', item.key.length * 2)
        // clear before start
        if (stks.length && !item.key.join().startsWith(_.last(stks)!.key.join())) {
          do {
            const stk = stks.pop()!
            text += `\n${_.pad('', stk.key.length * 2)}}${stk.type == 'object[]' ? '[]' : ''}`
          } while (stks.length && !item.key.join().startsWith(_.last(stks)!.key.join()))
        }
        if (item.type == 'object[]' || item.type == 'object') {
          // 处理 block
          stks.push(item)
          if (item.name) {
            text += `\n${pad}/** ${item.name} */`
          }
          text += `\n${pad}${_.last(item.key)}${item.nullable ? '?' : ''}: {`
        } else {
          if (stks.length) {
            // stks 不为空，继续处理 block
            if (item.key.join().startsWith(_.last(stks)!.key.join())) {
              // 还处于 block 当中
            } else {
              // 弹出最后一个block，可能会连续弹出
              do {
                const stk = stks.pop()!
                text += `\n${_.pad('', stk.key.length * 2)}}${stk.type == 'object[]' ? '[]' : ''}`
              } while (stks.length && !item.key.join().startsWith(_.last(stks)!.key.join()))
            }
          }
          if (item.name) {
            text += `\n${pad}/** ${item.name} */`
          }
          text += `\n${pad}${_.last(item.key)}${item.nullable ? '?' : ''}: ${item.type}`
        }
      }
      while (stks.length) {
        const stk = stks.pop()!
        text += `\n${_.pad('', stk.key.length * 2)}}${stk.type == 'object[]' ? '[]' : ''}`
      }
      text += '\n}'
      // request info 不需要处理数组类型
      // const _dataRaw = _(resolveResultInfo(apiInfo, true)).head()
      // if (!isQuery(apiInfo) && _dataRaw && ParamTypeMap[_dataRaw.paramType] == 'object[]') {
      //   text += '[]'
      // }
      return text
    }
    return 'never'

  }

  // 生成 ts vo 字符串
  export function genTsVo(apiInfo: ApiInfo.IApiInfo): string {
    const info = resolveResultInfo(apiInfo)
    if (_.isEmpty(info)) {
      return 'void'
    } else {
      const res = resolveListParams(info, apiInfo)
      let text = '{'
      const stks: typeof res = []
      for (const item of res) {
        const pad = _.pad('', item.key.length * 2)
        // clear before start
        if (stks.length && !item.key.join().startsWith(_.last(stks)!.key.join())) {
          do {
            const stk = stks.pop()!
            text += `\n${_.pad('', stk.key.length * 2)}}${stk.type == 'object[]' ? '[]' : ''}`
          } while (stks.length && !item.key.join().startsWith(_.last(stks)!.key.join()))
        }
        if (item.type == 'object[]' || item.type == 'object') {
          // 处理 block
          stks.push(item)
          // 处理属性注释
          if (item.name) {
            text += `\n${pad}/** ${item.name} */`
          }
          text += `\n${pad}${_.last(item.key)}${item.nullable ? '?' : ''}: {`
        } else {
          if (stks.length) {
            // stks 不为空，继续处理 block
            if (item.key.join().startsWith(_.last(stks)!.key.join())) {
              // 还处于 block 当中
            } else {
              // 弹出最后一个block，可能会连续弹出
              do {
                const stk = stks.pop()!
                text += `\n${_.pad('', stk.key.length * 2)}}${stk.type == 'object[]' ? '[]' : ''}`
              } while (stks.length && !item.key.join().startsWith(_.last(stks)!.key.join()))
            }
          }
          // 属性注释
          if (item.name) {
            text += `\n${pad}/** ${item.name} */`
          }
          text += `\n${pad}${_.last(item.key)}${item.nullable ? '?' : ''}: ${item.type}`
        }
      }
      while (stks.length) {
        const stk = stks.pop()!
        text += `\n${_.pad('', stk.key.length * 2)}}${stk.type == 'object[]' ? '[]' : ''}`
      }
      text += '\n}'
      const _dataRaw = _(resolveResultInfo(apiInfo, true)).head()
      if (_dataRaw && ParamTypeMap[_dataRaw.paramType] == 'object[]') {
        text += '[]'
      }
      return text
    }
    return 'never'
  }

  /** 对象类型 ts vo */
  export function getTsVo(): object {
    var object: object = []
    return []
  }
  /** 对象格式 ts dto */
  export function getTsDto(): object {
    return []
  }

  function isQuery(apiInfo: ApiInfo.IApiInfo) {
    return !_.isEmpty(apiInfo.urlParam)
  }
  function isFormData(apiInfo: ApiInfo.IApiInfo) {
    return apiInfo.baseInfo.apiRequestParamType == 2
  }

  // return [
  //   { key: ['foo'], type: 'object[]', name: '', optional: false },
  //   { key: ['foo','bar'], type: 'string', name: '职位', optional: true },
  // ]
  function resolveListParams(params: IUrlParam[] | IRequestInfo[], apiInfo: IApiInfo): {
    key: string[]
    type: typeof ParamTypeMap[number]
    name: string
    nullable: boolean
  }[] {
    if (isIRequestInfo(params)) {
      const res: {
        key: string[]
        type: typeof ParamTypeMap[number]
        name: string
        nullable: boolean
      }[] = []
      void function traverse(items: IRequestInfo[], keys: string[]) {
        for (let item of items) {
          if (item.structureID && item.structureID != '[]') {
            const structureID = +_.replace(item.structureID, /[^\d]/gi, '')
            if (structureID != null && apiInfo.dataStructureList[structureID]) {
              item.childList = apiInfo.dataStructureList[structureID].structureData
            }
          }
          if (item.paramType) {
            res.push({
              key: keys.concat(item.paramKey),
              type: ParamTypeMap[_.toNumber(item.paramType)] || `unknown-${item.paramType}`,
              name: item.paramName,
              nullable: item.paramNotNull == SBit.YES
            })
          }
          if (_.size(item.childList)) {
            traverse(item.childList, keys.concat(item.paramKey))
          }
        }
      }(params, [])
      return res
    } else {
      return params.map(param => {
        return {
          key: [param.paramKey],
          type: ParamTypeMap[_.toNumber(param.paramType)] || `unknown-${param.paramType}`,
          name: param.paramName,
          nullable: param.paramNotNull == SBit.YES
        } as {
          key: string[]
          type: typeof ParamTypeMap[number]
          name: string
          nullable: boolean
        }
      })
    }
  }
  function isIRequestInfo(params: IUrlParam[] | IRequestInfo[]): params is IRequestInfo[] {
    return params.length > 0 && _.some(params, 'childList')
  }

  function resolveResultInfo(apiInfo: IApiInfo, raw: boolean = false): IResultInfo[] {
    var structureID: number | null = null
    var paramList = apiInfo.resultInfo[0].paramList
    const _paramList: typeof paramList = []
    // fix: 06,Feb.2023 eolink strucreID 字段结构改动
    if (_.get(apiInfo, ['resultInfo', 0, 'paramList', 0, 'structureID'])) {
      structureID = _.get(apiInfo, ['resultInfo', 0, 'paramList', 0, 'structureID'])
    } else if (apiInfo.structureID != '[]') {
      structureID = +_.replace(apiInfo.structureID, /[^\d]/gi, '')
    }
    if (structureID != null) {
      // 处理api文档修改，解析 strucreID 和 updateData
      for (let i = paramList.length - 1; i >= 0; --i) {
        const item = paramList[i]
        if ('structureID' in item) {
          // 获取api详情的当前 structureData
          const data = apiInfo.dataStructureList[item.structureID!].structureData
          for (const d of data) {
            if ('updateData' in item && d.paramID in item.updateData) {
              const f = item.updateData[d.paramID]
              if (_.size(f.childList)) {
                d.childList = _.uniqBy(d.childList.concat(f.childList), 'paramKey')
              }
              if (f.paramType) {
                d.paramType = f.paramType
              }
              if (f.paramNotNull) {
                d.paramNotNull = f.paramNotNull
              }
            }
          }
          _paramList.push(...data)
        } else {
          _paramList.push(item)
        }
      }
    } else {
      // 直接返回，不需要处理
    }
    // traverse _childList
    void function traverse(items, map = {}) {
      for (const item of items) {
        const paramID = item.paramID
        if (map[paramID]) {
          const f = map[paramID]
          if (_.size(f.childList)) {
            // fix: childList 为空
            const _cl = _.uniqBy(_.concat(item?.childList, f.childList), 'paramKey')
            item.childList = _.compact(_cl)
          }
          if (f.paramType)
            item.paramType = f.paramType
          if (f.paramNotNull)
            item.paramNotNull = f.paramNotNull
        }
        _.isArray(item.childList) &&
          traverse(
            item.childList,
            map
          )
      }
    }(
      _paramList as IRequestInfo[],
      (_(apiInfo.resultInfo).flatMap(i => i.paramList).find({ structureID } as any) as any)?.updateData as Record<string, {
        paramNotNull: SBit
        childList: IRequestInfo[]
        paramType: `${string & keyof typeof ParamTypeMap}`
      }>
    )

    // 对返回结果进行 unwrap，忽略外层结构
    const found = (
      _.find(_paramList, {
        paramKey: 'data'
      }) ??
      _.find(_paramList, {
        paramKey: 'response'
      }) ??
      _.find(paramList, {
        paramKey: 'data'
      }) ?? _.find(paramList, {
        paramKey: 'response'
      }) ?? _.find(paramList)
    ) as IResultInfo
    if (raw) {
      // 获取外层结果
      return found ? [found] : []
    }
    return _.isEmpty(found && found.childList) ? [] : found.childList as any
  }
}